package com.cn.sl.server.api.system.user.service.impl

import cn.dev33.satoken.stp.SaLoginModel
import cn.dev33.satoken.stp.StpUtil
import com.cn.sl.server.api.system.user.dao.rdb.AdminUserDao
import com.cn.sl.server.api.system.user.exception.PasswordErrorException
import com.cn.sl.server.api.system.user.exception.UserDisabledExistException
import com.cn.sl.server.api.system.user.exception.UserNotExistException
import com.cn.sl.server.api.system.user.model.dto.AdminUserInfoDto
import com.cn.sl.server.api.system.user.model.dto.CheckPasswordDto
import com.cn.sl.server.api.system.user.model.dto.LoginDto
import com.cn.sl.server.api.system.user.model.dto.TokenDto
import com.cn.sl.server.api.system.user.model.dto.UpdateLoginUserInfoDto
import com.cn.sl.server.api.system.user.model.dto.UpdatePasswordDto
import com.cn.sl.server.api.system.user.model.entity.QAdminUserEntity
import com.cn.sl.server.api.system.user.model.entity.UserStatus
import com.cn.sl.server.api.system.user.service.LoginService
import com.cn.sl.server.api.system.user.util.encrypt
import com.cn.sl.server.api.system.user.util.passwordCompare
import com.cn.sl.server.common.annotation.transaction.Commit
import com.cn.sl.server.common.annotation.transaction.ReadOnly
import com.cn.sl.server.common.exception.BusinessException
import com.querydsl.jpa.impl.JPAQueryFactory
import org.dromara.hutool.core.codec.binary.Base64
import org.dromara.hutool.core.data.PhoneUtil
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service

@Service
class LoginServiceImpl(
    val adminUserDao: AdminUserDao,
    val jpaQueryFactory: JPAQueryFactory
) : LoginService {

    @ReadOnly
    override fun login(
        dto: LoginDto,
    ): TokenDto {
        return if (PhoneUtil.isPhone(dto.username)) {
            adminUserDao.findByPhone(dto.username)
        } else {
            val users = adminUserDao.findAllByUsername(dto.username)
            if (users.size == 1) {
                users.first()
            } else if (users.size > 1) {
                throw BusinessException("存在多个同名用户，请使用手机号码登录", HttpStatus.NOT_ACCEPTABLE)
            } else {
                null
            }
        }?.let {
            //校验密码是否一样
            if (!passwordCompare(it.password, String(Base64.decode(dto.password)), it.salt)) {
                throw PasswordErrorException()
            }

            if (it.status == UserStatus.DISABLED) {
                throw UserDisabledExistException()
            }

            val loginModel = SaLoginModel().apply {
                this.device = dto.client
                this.isLastingCookie = dto.rememberMe
            }
            StpUtil.login(it.id, loginModel)

            adminUserDao.save(it)
            return TokenDto(StpUtil.getTokenValue())
        } ?: throw UserNotExistException()
    }

    @ReadOnly
    override fun info(): AdminUserInfoDto {
        return AdminUserInfoDto(adminUserDao.findById(StpUtil.getLoginIdAsLong()))
    }

    @ReadOnly
    override fun logout() {
        StpUtil.logout()
    }

    @ReadOnly
    override fun checkPassword(dto: CheckPasswordDto): Boolean {
        return adminUserDao.findById(StpUtil.getLoginIdAsLong()).let {
            passwordCompare(it.password, String(Base64.decode(dto.password)), it.salt)
        }
    }

    @Commit
    override fun updatePassword(dto: UpdatePasswordDto) {
        adminUserDao.findById(StpUtil.getLoginIdAsLong()).let {
            val (encryptPassword, salt) = encrypt(String(Base64.decode(dto.newPassword)))
            val entity = QAdminUserEntity.adminUserEntity
            jpaQueryFactory.update(entity)
                .set(entity.password, encryptPassword)
                .set(entity.salt, salt)
                .where(entity.id.eq(it.id))
                .execute()
        }
    }

    @Commit
    override fun updateUserInfo(dto: UpdateLoginUserInfoDto) {
        adminUserDao.findById(StpUtil.getLoginIdAsLong()).let {
            it.apply {
                this.avatarId = dto.avatarId
                this.username = dto.username
                this.phone = dto.phone
                this.homePageId = dto.homePageId
            }
            adminUserDao.save(it)
        }
    }
}